package com.cj.file.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cj.common.exception.BaseException;
import com.cj.common.utils.FileUploadUtils;
import com.cj.file.constant.FileConstant;
import com.cj.file.entity.FileProperties;
import com.cj.file.service.FileService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.type.GeometryTypeImpl;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Polygon;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Encoder;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @Author cheng jun
 * @Description:
 * @Date 2021/7/8 17:29
 */
@Service("localFileServiceImpl")
@EnableConfigurationProperties({FileProperties.class})
@Slf4j
public class LocalFileServiceImpl implements FileService {

    @Resource
    private FileProperties fileProperties;

    @Override
    public Map<String, Object> upLoad(MultipartFile file) {
        // 获取文件原始名称
        String originalFilename = file.getOriginalFilename();

        // 获取文件后缀
        String extension = FilenameUtils.getExtension(originalFilename);

        // 新的名称
        String newFilename = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date()) +
                UUID.randomUUID().toString().replace("-", "") + "." + extension;

        // 获取文件大小
        long fileSize = file.getSize();
        if (fileSize > this.sizeConvert()) {
            throw new BaseException("文件太大");
        }

        // 获取文件类型
        String fileType = file.getContentType();

        // 存储路径
        String dirPath = new SimpleDateFormat("yyyyMMdd").format(new Date());
        // 根据日期生成目录
        String dateDirPath = fileProperties.getPath() + "/" + dirPath;
        File dateDir = new File(dateDirPath);
        if (!dateDir.exists()) {
            dateDir.mkdirs();
        }
        try {
            // 文件上传
            file.transferTo(new File(dateDir, newFilename));
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BaseException(e.getMessage());
        }

        Map<String, Object> map = new HashMap<>();
        map.put("originalName", originalFilename);
        map.put("newName", newFilename);
        map.put("extension", extension);
        map.put("size", fileSize);
        map.put("type", fileType);
        map.put("path", dirPath);
        return map;
    }

    @Override
    public void downLoad(Map<String, Object> map, HttpServletResponse response) {
        Assert.notNull(map.get("path"), "文件路径不能为空");
        Assert.notNull(map.get("openStyle"), "打开方式不能为空");

        FileInputStream fileInputStream = null;
        ServletOutputStream servletOutputStream = null;
        try{
            // 获取文件输入流
            String realPath = fileProperties.getPath() + map.get("path");
            fileInputStream = new FileInputStream(new File(realPath, "存储新名字"));

            // 设置下载名(attachment:下载，inline：在线打开)
            response.setHeader("content-disposition", String.valueOf(map.get("openStyle")) + ";fileName=" +
                    URLEncoder.encode("存储旧名字", "UTF-8"));
            // 解决跨域问题
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Cache-Control", "no-cache");
            // 获取响应输出流
            servletOutputStream = response.getOutputStream();

            // 第四种方式：文件拷贝
            IOUtils.copy(fileInputStream, servletOutputStream);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BaseException(e.getMessage());
        } finally {
            // 关闭输入输出流
            try {
                IOUtils.close(fileInputStream);
                IOUtils.close(servletOutputStream);
            } catch (IOException e) {
                log.error(e.getMessage());
                throw new BaseException(e.getMessage());
            }
            /*IOUtils.closeQuietly(fileInputStream);
            IOUtils.closeQuietly(servletOutputStream);*/
        }
    }

    @Override
    public String fileDisplay(Map<String, Object> map) {
        Assert.notNull(map.get("filePath"), "文件路径不能为空");
        Assert.notNull(map.get("newName"), "文件名称不能为空");

        // 获取文件路径
        String path = fileProperties.getPath() + map.get("filePath");

        // 获取base64字符串
        String base64;
        FileInputStream fileInputStream = null;
        try {
            File file = new File(path, String.valueOf(map.get("newName")));
            fileInputStream = new FileInputStream(file);
            byte[] buffer = new byte[(int)file.length()];
            fileInputStream.read(buffer);
            base64 = new BASE64Encoder().encode(buffer);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BaseException(e.getMessage());
        } finally {
            // 关闭输入流
            try {
                fileInputStream.close();
            } catch (IOException e) {
                log.error(e.getMessage());
                throw new BaseException(e.getMessage());
            }
        }

        return base64;
    }

    @Override
    public boolean fileDelete(Map<String, Object> map) {
        Assert.notNull(map.get("filePath"), "文件路径不能为空");
        Assert.notNull(map.get("newName"), "文件名称不能为空");

        // 获取文件路径
        String path = fileProperties.getPath() + map.get("filePath");

        // 删除
        File file = new File(path, String.valueOf(map.get("newName")));
        boolean flag = false;
        if (file.exists()) {
            flag = file.delete();
        }
        return flag;
    }

    @Override
    public void htmlToWorldExport(Map<String, Object> map, HttpServletResponse response) {
        if (map == null) {
            log.error("worldUtils2-dispatchTask实体不能为空");
            return;
        }

        FileOutputStream fileOutputStream = null;
        PrintStream printStream = null;

        OutputStream toClient = null;
        InputStream fis = null;
        try {
            StringBuilder s = new StringBuilder();
            s.append("<!DOCTYPE html>\n" +
                    "<html lang=\"en\">\n" +
                    "  <head>\n" +
                    "    <meta charset=\"UTF-8\" />\n" +
                    "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n" +
                    "    <title>Document</title>\n" +
                    "  </head>\n" +
                    "  <body>\n" +
                    "    <div\n" +
                    "      class=\"docBox\"\n" +
                    "      style=\"\n" +
                    "        width: 50vw;\n" +
                    "        margin: 0 auto;\n" +
                    "        text-align: center;\n" +
                    "        display: flex;\n" +
                    "        flex-direction: column;\n" +
                    "        align-items: center;\n" +
                    "      \"\n" +
                    "    >\n" +
                    "      <div class=\"titleBox\" style=\"width: 50%\">\n" +
                    "        <h1>玉溪市污染源在线数据信息中心发现环境问题移交签收单</h1>\n" +
                    "      </div>\n" +
                    "      <div\n" +
                    "        class=\"textBox\"\n" +
                    "        style=\"width: 100%; text-align: right; margin-bottom: 20px\"\n" +
                    "      >\n" +
                    "        编号: " + map.get("a") + "\n" +
                    "      </div>\n" +
                    "      <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"1\">\n" +
                    "        <tr>\n" +
                    "          <td width=\"20%\">环境问题</td>\n" +
                    "          <td colspan=\"3\">" + String.valueOf(map.get("a")).replace(" ", "&nbsp;") + "</td>\n" +
                    "        </tr>\n" +
                    "        <tr>\n" +
                    "          <td>资料清单</td>\n" +
                    "          <td colspan=\"3\"> " + map.get("a") + "</td>\n" +
                    "        </tr>\n" +
                    "        <tr>\n" +
                    "          <td>信息中心意见</td>\n" +
                    "          <td colspan=\"3\">" + map.get("a") + "</td>\n" +
                    "        </tr>\n" +
                    "        <tr>\n" +
                    "          <td>移送部门</td>\n" +
                    "          <td>" + map.get("a") + "</td>\n" +
                    "          <td width=\"20%\">接受部门</td>\n" +
                    "          <td>" + map.get("a") + "</td>\n" +
                    "        </tr>\n" +
                    "        <tr>\n" +
                    "          <td>移送部门签字</td>\n" +
                    "          <td></td>\n" +
                    "          <td>接受部门签字</td>\n" +
                    "          <td></td>\n" +
                    "        </tr>\n" +
                    "        <tr>\n" +
                    "          <td>移交时间</td>\n" +
                    "          <td colspan=\"3\">" + map.get("a") + "</td>\n" +
                    "        </tr>\n" +
                    "      </table>\n" +
                    "    </div>\n" +
                    "  </body>\n" +
                    "</html>\n");
            String path = "downloadPath" + "/" + new SimpleDateFormat("yyyyMMdd").format(new Date());
            File file = new File(path);
            if(!file.exists()){
                file.mkdirs();
            }
            String uuid = String.valueOf(UUID.randomUUID()).replace("-", "");
            String dirPath = path + "/" + uuid + "移交签收单.html";
            File file2 = new File(dirPath);
            try {
                // 假如文件不存在,需创建
                if (!file2.exists()) {
                    file2.createNewFile();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            fileOutputStream = new FileOutputStream(dirPath);
            printStream = new PrintStream(fileOutputStream);
            printStream.println(s);
            fileOutputStream.flush();
            printStream.flush();

            // 以流的形式下载文件。
            fis = new BufferedInputStream(new FileInputStream(dirPath));
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            response.reset();
            // 设置response的Header  这里意义不大 直接被vue前台替换了 所以可以删掉
            response.addHeader("Content-Disposition", "attachment;filename*=utf-8''" + URLEncoder.encode("移交签收单.docx", "UTF-8"));
            toClient = new BufferedOutputStream(response.getOutputStream());
            // 注意这里很重要 vue的回调里根据ContentType类判断是否是下载的操作，这里不要改
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("UTF-8");
            // 解决跨域问题
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Cache-Control", "no-cache");
            toClient.write(buffer);
            toClient.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
                if (printStream != null) {
                    printStream.close();
                }
                if (fis != null) {
                    fis.close();
                }
                if (toClient != null) {
                    fileOutputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void worldExport(Map<String, Object> map, HttpServletResponse response) {
        // 1.创建配置类，java -jar yuxi.jar打包编码问题解决方案：java -Dfile.encoding=UTF-8 -jar yuxi.jar
        Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        Writer out = null;
        // 2.设置模板所在的目录
        try {
            configuration.setClassForTemplateLoading(this.getClass(), "/template");
            // 3.设置字符集
            configuration.setDefaultEncoding("utf-8");
            // 4.加载模板;
            Template template = configuration.getTemplate("测试" + ".ftl");
            // 6.创建 Writer 对象
            String path = "downloadPath" + "/" + new SimpleDateFormat("yyyyMMdd").format(new Date());
            File file = new File(path);
            if(!file.exists()){
                file.mkdirs();
            }
            String uuid = String.valueOf(UUID.randomUUID()).replace("-", "");
            String dirPath = path + "/" + uuid + "测试" + ".docx";
            File file2 = new File(dirPath);
            try {
                // 假如文件不存在,需创建
                if (!file2.exists()) {
                    file2.createNewFile();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            out = new FileWriter(file2);
            // 7.保存数据
            template.process(map, out);

            // 以流的形式下载文件。
            InputStream fis = new BufferedInputStream(new FileInputStream(dirPath));
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            response.reset();
            // 设置response的Header  这里意义不大 直接被vue前台替换了 所以可以删掉
            response.addHeader("Content-Disposition", "attachment;filename*=utf-8''" + URLEncoder.encode("测试" + ".docx", "UTF-8"));
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            // 注意这里很重要 vue的回调里根据ContentType类判断是否是下载的操作，这里不要改
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("UTF-8");
            // 解决跨域问题
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Cache-Control", "no-cache");
            toClient.write(buffer);
            toClient.flush();
            fis.close();
            toClient.close();
        } catch (Exception e) {
            log.error("导出world报错:" + e);
            e.printStackTrace();
        } finally {
            try {
                // 8.关闭 Writer 对象
                if (out != null) {
                    out.close();
                }
            } catch (Exception e) {
                log.error("关闭Writer对象报错:" + e);
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean getIfInclude(String jd, String wd) {
        InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("geojson/yuxi.json");
        boolean pointIsInPolygon = false;
        try {
            // 初始化
            GeometryJSON g = new GeometryJSON();
            Polygon polygon = g.readPolygon(stencilsetStream);
            GeometryFactory gf = new GeometryFactory();
            pointIsInPolygon = polygon.contains(gf.createPoint(new Coordinate(Double.parseDouble(jd), Double.parseDouble(wd))));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return pointIsInPolygon;
    }

    @Override
    public Map<String, Object> shpFileImportCalculate(MultipartFile file) {
        JSONArray array = new JSONArray();
        try (InputStream inputStream = file.getInputStream()) {
            File outPath = FileUploadUtils.getFilePath(file, "shp", false);
            if (outPath.exists()) {
                outPath.delete();
            }
            File unzip = ZipUtil.unzip(inputStream, outPath, Charset.forName("GBK"));
            List<File> files = FileUtil.loopFiles(unzip, pathname -> StrUtil.equals(FileUtil.getSuffix(pathname), "shp"));
            for (File shpFile : files) {
                SimpleFeatureSource featureSource = readSHP(shpFile, null);
                if (featureSource != null) {
                    array = readLand(featureSource,array);
                }
            }
        } catch (Exception | Error e) {
            e.printStackTrace();
        }

        List<Map<String, Object>> list = new ArrayList<>();
        for (Object o : array) {
            String s1 = String.valueOf(o);
            StringBuilder stringBuilder = analysisShp(s1);
            if (stringBuilder == null) {
                continue;
            }

            Map<String, Object> map = new HashMap<>();
            String mc = (String) JSONObject.parseObject(String.valueOf(JSONObject.parseObject(s1).get("properties"))).get("mc");
            map.put("mc", mc);

            // 遍历计算每个区域压盖面积
            List<Map<String, Object>> childList = new ArrayList<>();
            List<String> jSONObjectList = JSONObject.parseObject("region", List.class);
            for (String s : jSONObjectList) {
                Map<String, Object> childMap = new HashMap<>();
                String table = "yx_shj_yxzjyysydbhq";
                if (Objects.equals("玉溪乡镇级饮用水源地保护区", s)) {
                    table = "yx_shj_yxzjyysydbhq";
                    childMap.put("place", s);
                } else if (Objects.equals("玉溪千吨万人保护区", s)) {
                    table = "yx_shj_yxqdwrbhq";
                    childMap.put("place", s);
                }

                try {
//                    List<Map<String, Object>> calculateQlandArea = pollutionMapper.calculateQlandArea(String.valueOf(stringBuilder), table);
                    // todo
                    /*<!--计算压盖面积-->
                    <select id="calculateQlandArea" parameterType="String" resultType="map">
                        SELECT
                            round(cast(Sum(ST_Area(st_transform(ST_Intersection(ST_GeomFromText(#{geometry}, 4490), w.geom), 4523))) *0.001 as numeric), 4) AS area,
                            w.sydmc
                        FROM
                            ${table} w
                        where ST_Intersects(ST_GeomFromText(#{geometry}, 4490), w.geom)
                        GROUP BY 2
                    </select>*/
                    childMap.put("details", "todo");
                } catch (Exception e) {
                    log.error("空间查询压盖面积sql报错：" + e + "," + "传参：" + stringBuilder + "," + "表名：" + table);
                    e.printStackTrace();
                }

                childList.add(childMap);
            }

            map.put("region", childList);
            list.add(map);
        }

        Map<String, Object> data = new HashMap<>();
        data.put("geometry", array);
        data.put("element", list);

        return data;
    }

    /**
     * @description: 解析shp
     **/
    public StringBuilder analysisShp(String str) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("MULTIPOLYGON(");
        List<String> list = JSONArray.parseArray(JSONObject.parseObject(String.valueOf(JSONObject.parseObject(str).get("geometry"))).get("coordinates").toString(), String.class);
        if (list == null || list.size() == 0) {
            return null;
        }

        List list1 = JSONObject.parseObject(list.get(0), List.class);
        if (list1 ==  null || list1.size() == 0) {
            return null;
        }

        for (int i1 = 0; i1 < list1.size(); i1++) {
            stringBuilder.append("((");
            List o = (List) list1.get(i1);
            if (o == null || o.size() == 0) {
                continue;
            }

            for (int i2 = 0; i2 < o.size(); i2++) {
                List list4 = JSONObject.parseObject(String.valueOf(o.get(i2)), List.class);
                if (list4 == null || list4.size() == 0) {
                    continue;
                }

                for (int i3 = 0; i3 < list4.size(); i3++) {
                    stringBuilder.append(list4.get(i3));
                    if (i3 < list4.size() - 1) {
                        stringBuilder.append(" ");
                    }
                }

                if (i2 < o.size() - 1) {
                    stringBuilder.append(",");
                }
            }

            stringBuilder.append("))");
            if (i1 < list1.size() - 1) {
                stringBuilder.append(",");
            }
        }
        stringBuilder.append(")");

        return stringBuilder;
    }

    /**
     * 读取shp文件
     * @param shpFile
     * @return
     */
    public SimpleFeatureSource readSHP(File shpFile, String characterSet) {
        SimpleFeatureSource featureSource = null;
        ShapefileDataStore shpDataStore = null;
        try {
            shpDataStore = new ShapefileDataStore(shpFile.toURI().toURL());
            characterSet = StrUtil.isBlank(characterSet) ? getShapeFileCharsetName(shpFile.getPath()) : characterSet;
            Charset charset = Charset.forName(characterSet);
            shpDataStore.setCharset(charset);
            String tableName = shpDataStore.getTypeNames()[0];
            featureSource = shpDataStore.getFeatureSource(tableName);
        } catch (Exception e) {
        } finally {
            if (shpDataStore != null) {
                shpDataStore.dispose();
            }
        }
        return featureSource;
    }

    /**
     * 高版本arcgis生成的shp会有一个cpg文件记录编码格式
     *
     * @param path shp文件路径
     * @return 编码格式
     */
    public static String getShapeFileCharsetName(String path) {
        StringBuilder sb = new StringBuilder(path);
        sb.replace(path.length() - 4, path.length(), ".cpg");
        File file = new File(sb.toString());
        String encode = "GBK";
        if (!file.exists() || !file.isFile()) {
            return encode;
        }
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String tempString;
            // 一次读入一行，直到读入null为文件结束
            while ((tempString = reader.readLine()) != null) {
                // 显示行号
                if ("UTF-8".equals(tempString.toUpperCase())) {
                    encode = "UTF-8";
                    break;
                }
                break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return encode;
    }

    private JSONArray readLand(SimpleFeatureSource featureSource, JSONArray array) {
        SimpleFeatureType schema = featureSource.getSchema();
        SimpleFeatureIterator features = null;
        // 开始写入数据
        try {
            FeatureJSON fjson = new FeatureJSON(new GeometryJSON(20));// 此处会控制生成geojson的坐标的小数点精度
            CoordinateReferenceSystem crs = getShpCRS(schema);
            // CGCS2000坐标系
            CoordinateReferenceSystem worldCRS = DefaultGeographicCRS.WGS84;
            boolean lenient = true;
            MathTransform transform = CRS.findMathTransform(crs, worldCRS, lenient);
            features = featureSource.getFeatures().features();
            while (features.hasNext()) {
                SimpleFeature feature = features.next();
                // 设置要素的属性信息和空间信息
                Geometry polygon = JTS.transform((Geometry) feature.getDefaultGeometry(), transform);
                feature.setDefaultGeometry(polygon);
                StringWriter writer = new StringWriter();
                fjson.writeFeature(feature, writer);
                array.add(writer.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (features != null) {
                features.close();
            }
        }

        return array;
    }

    /**
     * 获取Shp文件的坐标参考系
     *
     * @param schema
     * @return
     */
    private CoordinateReferenceSystem getShpCRS(SimpleFeatureType schema) {
        Assert.notNull(schema, "Shp文件要素类型不能为空");
        // 获取Shp文件的坐标系
        CoordinateReferenceSystem crs = null;
        List<AttributeDescriptor> descList = schema.getAttributeDescriptors();
        for (AttributeDescriptor dec : descList) {
            if (dec instanceof GeometryDescriptor) {
                crs = ((GeometryTypeImpl) dec.getType()).getCoordinateReferenceSystem();
                break;
            }
        }
        return crs;
    }

    public long sizeConvert() {
         String storageSize = fileProperties.getSize();

         // 获取数据单位
        String unit = storageSize.substring(storageSize.length() - 1);
        int size = Integer.parseInt(storageSize.substring(0, storageSize.length() - 1));
        long bSize = 0L;
        switch(unit){
            case FileConstant.G :
                bSize = size * 1024 * 1024 * 1024;
                break;
            case FileConstant.M :
                bSize = size * 1024 * 1024;
                break;
            case FileConstant.K :
                bSize = size * 1024;
                break;
            case FileConstant.B :
                bSize = size;
                break;
            default :
        }

        return bSize;
    }
}
